/*******************************************************************************
* File Name          : W25Q128.c
* Author             : Yangjie Gu
* Description        : This file provides all the W25Q128 functions.

* History:
*  10/23/2017 : W25Q128 V1.00
*******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "W25Q128.h"
#include "cmsis_os.h"

/* Function prototypes -------------------------------------------------------*/
uint8_t W25Q128_Send_Cmd(uint8_t const cmd, uint8_t const dummys)
{
    uint8_t err = 0;
    uint8_t pData[8] = {0};
    pData[0] = cmd;
    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, pData, 1 + dummys, W25Q128_SPI_TRANSMIT_DELAY))
        err = 1;
    return err;
}

uint8_t W25Q128_Send_Data(uint8_t *const pData, const uint16_t num)
{
    uint8_t err = 0;
    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, pData, num, W25Q128_SPI_TRANSMIT_DELAY))
        err = 1;
    return err;
}

uint8_t W25Q128_Read_Data(uint8_t *const pData, const uint16_t num)
{
    uint8_t err = 0;
    if (HAL_OK != HAL_SPI_Receive(W25Q128_SPI_HANDLE, pData, num, W25Q128_SPI_TRANSMIT_DELAY))
        err = 1;
    return err;
}

uint8_t W25Q128_PowerDown(void)
{
    W25Q128_SPI_CS_LOW();

    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
    {
        return 1;
    }

    if (W25Q128_Send_Cmd(W25Q128_CMD_POWER_DOWN, W25Q128_DEFAULT_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_RealsePowerDown_GetDeviceId(uint8_t *const pId)
{
    uint8_t temp;
    W25Q128_SPI_CS_LOW();

    if (W25Q128_Send_Cmd(W25Q128_CMD_RELEASE_POWER_DOWN, W25Q128_CMD_RELEASE_POWER_DOWN_GET_DEVICE_ID_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 1;
    }

    if (pId == NULL)
    {
        if (W25Q128_Read_Data(&temp, 1))
        {
            W25Q128_SPI_CS_HIGH();
            return 2;
        }
    }
    else
    {
        if (W25Q128_Read_Data(pId, 1))
        {
            W25Q128_SPI_CS_HIGH();
            return 3;
        }
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_WriteEnable(void)
{
    W25Q128_SPI_CS_LOW();

    if (W25Q128_Send_Cmd(W25Q128_CMD_WRITE_ENABLE, W25Q128_DEFAULT_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 1;
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_WriteDisable(void)
{
    W25Q128_SPI_CS_LOW();

    if (W25Q128_Send_Cmd(W25Q128_CMD_WRITE_DISABLE, W25Q128_DEFAULT_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 1;
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_ReadStatusRegister(uint8_t *const pStatusReg)
{
    W25Q128_SPI_CS_LOW();

    if (W25Q128_Send_Cmd(W25Q128_CMD_READ_STATUS_REGISTER, W25Q128_DEFAULT_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 1;
    }

    if (W25Q128_Read_Data(pStatusReg, 1))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_IsBusy(void)
{
    uint8_t ret = 0;
    uint8_t StatusReg = 0;

    if (W25Q128_ReadStatusRegister(&StatusReg))
    {
        ret = 2;
    }
    else
    {
        ret = W25Q128_GET_BUSY_FALG(StatusReg);
    }

    return ret;
}

uint8_t W25Q128_WaitBusyTimeOut(uint8_t milliseconds)
{
    uint8_t ret = 0;
    uint8_t count = milliseconds;

    for (; count > 0; count--)
    {
        if (W25Q128_IsBusy() == 0)
            break;

        osDelay(1);
    }

    if (count == 0)
        ret = 1;

    return ret;
}

uint8_t W25Q128_WaitChipEraseTimeOut(uint8_t seconds)
{
    uint8_t ret = 0;
    uint8_t count = seconds;

    for (; count > 0; count--)
    {
        if (W25Q128_IsBusy() == 0)
            break;

        osDelay(5000);
    }

    if (count == 0)
        ret = 1;

    return ret;
}

uint8_t W25Q128_WaitSectorEraseTimeOut(uint8_t tenmilliseconds)
{
    uint8_t ret = 0;
    uint8_t count = tenmilliseconds;

    for (; count > 0; count--)
    {
        if (W25Q128_IsBusy() == 0)
            break;

        osDelay(10);
    }

    if (count == 0)
        ret = 1;

    return ret;
}

uint8_t W25Q128_SetStatusRegister(uint8_t Regvalue)
{
    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
    {
        return 1;
    }

    W25Q128_WriteEnable();

    W25Q128_SPI_CS_LOW();

    if (W25Q128_Send_Cmd(W25Q128_CMD_WRITE_STATUS_REGISTER, W25Q128_DEFAULT_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    if (W25Q128_Send_Data(&Regvalue, W25Q128_SET_STATUS_REGISTER_DATA_NUM))
    {
        W25Q128_SPI_CS_HIGH();
        return 3;
    }

    W25Q128_SPI_CS_HIGH();

    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
        return 4;

    return 0;
}

uint8_t W25Q128_ReadData(uint8_t *const pData, uint16_t num, uint32_t address)
{
    uint8_t cmd[8] = {0};

    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
        return 1;

    W25Q128_SPI_CS_LOW();

    cmd[0] = W25Q128_CMD_READ_DATA;
    cmd[1] = ((address >> 16u) & 0xff);
    cmd[2] = ((address >> 8u) & 0xff);
    cmd[3] = ((address >> 0u) & 0xff);

    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, cmd, 4, W25Q128_SPI_TRANSMIT_DELAY))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    if (HAL_OK != W25Q128_Read_Data(pData, num))
    {
        W25Q128_SPI_CS_HIGH();
        return 3;
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_ChipErase(void)
{
    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
        return 1;

    W25Q128_WriteEnable();

    W25Q128_SPI_CS_LOW();

    if (W25Q128_Send_Cmd(W25Q128_CMD_CHIP_ERASE, W25Q128_DEFAULT_DUMMYS))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    W25Q128_SPI_CS_HIGH();

    if (W25Q128_WaitChipEraseTimeOut(W25Q128_CHIP_EARSE_TIMEOUT_FIVESECONDS))
        return 3;

    return 0;
}

uint8_t W25Q128_PageProgram(uint8_t *const pData, uint16_t num, uint32_t address)
{
    uint8_t cmd[8] = {0};

    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
        return 1;

    W25Q128_WriteEnable();

    W25Q128_SPI_CS_LOW();

    cmd[0] = W25Q128_CMD_PAGE_PROGRAM;
    cmd[1] = ((address >> 16u) & 0xff);
    cmd[2] = ((address >> 8u) & 0xff);
    cmd[3] = ((address >> 0u) & 0xff);
    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, cmd, 4, W25Q128_SPI_TRANSMIT_DELAY))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, pData, num, W25Q128_SPI_TRANSMIT_DELAY))
    {
        W25Q128_SPI_CS_HIGH();
        return 3;
    }

    W25Q128_SPI_CS_HIGH();

    return 0;
}

uint8_t W25Q128_WriteData(uint8_t *const pData, uint16_t num, uint32_t address)
{
    uint16_t count = num / W25Q128_PAGE_SIZE;
    uint16_t left = num % W25Q128_PAGE_SIZE;
    uint8_t i = 0;

    if (num == 0)
    {
        return 1;
    }

    for (i = 0; i < count; i++)
    {
        if (W25Q128_PageProgram(pData + i * W25Q128_PAGE_SIZE, W25Q128_PAGE_SIZE, address + i * W25Q128_PAGE_SIZE))
            return 2;
    }

    if (left)
    {
        if (W25Q128_PageProgram(pData + i * W25Q128_PAGE_SIZE, left, address + i * W25Q128_PAGE_SIZE))
            return 3;
    }

    return 0;
}

uint8_t W25Q128_EraseSector(uint32_t address)
{
    uint8_t cmd[8] = {0};

    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
        return 1;

    W25Q128_WriteEnable();

    W25Q128_SPI_CS_LOW();

    cmd[0] = W25Q128_CMD_SECTOR_ERASE;
    address &= (~W25Q128_SECTOR_ADDRESS_ALIGNMENT_MASK);
    cmd[1] = ((address >> 16u) & 0xff);
    cmd[2] = ((address >> 8u) & 0xff);
    cmd[3] = ((address >> 0u) & 0xff);
    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, cmd, 4, W25Q128_SPI_TRANSMIT_DELAY))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    W25Q128_SPI_CS_HIGH();

    if (W25Q128_WaitSectorEraseTimeOut(W25Q128_SECTOR_EARSE_TIMEOUT_TENMILLISECONDS))
        return 3;

    return 0;
}

uint8_t W25Q128_Erase64KBlock(uint32_t address)
{
    uint8_t cmd[8] = {0};

    if (W25Q128_WaitBusyTimeOut(W25Q128_BUSY_TIMEOUT))
        return 1;

    W25Q128_WriteEnable();

    W25Q128_SPI_CS_LOW();

    cmd[0] = W25Q128_CMD_64K_BLOCK_ERASE;
    address &= (~W25Q128_SECTOR_ADDRESS_ALIGNMENT_MASK);
    cmd[1] = ((address >> 16u) & 0xff);
    cmd[2] = ((address >> 8u) & 0xff);
    cmd[3] = ((address >> 0u) & 0xff);
    if (HAL_OK != HAL_SPI_Transmit(W25Q128_SPI_HANDLE, cmd, 4, W25Q128_SPI_TRANSMIT_DELAY))
    {
        W25Q128_SPI_CS_HIGH();
        return 2;
    }

    W25Q128_SPI_CS_HIGH();

    if (W25Q128_WaitSectorEraseTimeOut(W25Q128_SECTOR_EARSE_TIMEOUT_TENMILLISECONDS))
        return 3;

    return 0;
}
